<!DOCTYPE html>
<html>
<!-- 
    在我写下这段话的时候, 正常运行本程序需要threejs版本为v123, arcgis js api版本为v4.21~4.22,
    后续版本情况暂无法确定, 也没有太多时间去测试确认是否真的与版本相关, 也没时间去测试其他更低版本的情况
     -- 2022.5.16
 -->

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
    <title>三维热力图</title>
    <style>
        html,
        body,
        #viewDiv {
            padding: 0;
            margin: 0;
            height: 100%;
            width: 100%;
        }
    </style>

    <link rel="stylesheet" href="https://js.arcgis.com/4.22/esri/themes/light/main.css" />
    <script src="./lib/heatmap.js/heatmap.min.js"></script>
    <script src="./lib/threejs/build/three.min.js"></script>
    <script src="https://js.arcgis.com/4.22/"></script>

    <script type="module">
        import * as myShader from "./shaders.js";

        const createHeatmapAndGreyMap = () => {
            // 随机生成点位和值
            let len = 100;
            let width = 1000;
            let height = 1000;
            let points = [];
            let max = 0;
            for (let i = 0; i < len; i++) {
                let val = Math.floor(Math.random() * 100);
                max = Math.max(max, val);
                points.push({
                    x: Math.floor(Math.random() * width),
                    y: Math.floor(Math.random() * height),
                    value: val
                });
            }

            // 热力图
            let heatmapDom = document.createElement("div");
            heatmapDom.style.width = "1000px";
            heatmapDom.style.height = "1000px";
            heatmapDom.style.display = "none";
            document.body.appendChild(heatmapDom);
            let heatmap = window.h337.create({
                container: heatmapDom
            });
            heatmap.setData({
                max: max,
                data: points
            });

            // 灰度图
            let greyMapDom = document.createElement("div");
            greyMapDom.style.width = "1000px";
            greyMapDom.style.height = "1000px";
            greyMapDom.style.display = "none";
            document.body.appendChild(greyMapDom);
            let greyMap = window.h337.create({
                container: greyMapDom,
                gradient: {
                    "0": "black",
                    "1.0": "white"
                }
            });
            greyMap.setData({
                max: max,
                data: points
            });

            return {
                heatmap,
                greyMap,
            };
        };

        require(["esri/Map", "esri/views/SceneView", "esri/views/3d/externalRenderers"], (Map, SceneView, externalRenderers) => {
            const init = async () => {
                const map = new Map({
                    basemap: "topo-vector",
                });

                const view = new SceneView({
                    container: "viewDiv",
                    map: map,
                    camera: {
                        fov: 55,
                        heading: 356.26342993591527,
                        tilt: 45.41023092670883,
                        position: {
                            type: "point",
                            x: 12622614.003062177,
                            y: 2497751.738876022,
                            z: 131106.37478592247,
                            spatialReference: {
                                wkid: 3857
                            }
                        }
                    }
                });

                let { heatmap, greyMap } = createHeatmapAndGreyMap();
                let myExternalRenderer = {
                    threejsObj: {
                        renderer: null,
                        scene: null,
                        camera: null,
                        geometry: null,
                        material: null,
                        object3d: null,
                        light: null,
                    },

                    setup: function (context) {
                        // 创建threejs的renderer对象
                        const renderer = new THREE.WebGLRenderer({
                            context: context.gl,
                            premultipliedAlpha: false,
                            antialias: true,
                        });
                        renderer.setClearColor(new THREE.Color("rgb(0, 0, 0)"), 0.0);

                        // 获取设备分辨率系数并设置至renderer
                        const pixelRatio = window.devicePixelRatio;
                        renderer.setPixelRatio(pixelRatio);

                        // 设置renderer视窗大小
                        renderer.setSize(view.width, view.height, false);
                        // renderer.setViewport(0, 0, view.width, view.height);
                        this.threejsObj.renderer = renderer;

                        // 定义renderer是否自动清除深度缓存
                        renderer.autoClearDepth = false;
                        // 定义renderer是否自动清除模板缓存
                        renderer.autoClearStencil = false;
                        // 定义renderer是否自动清除颜色缓存
                        renderer.autoClearColor = false;

                        // 修改renderer对象的setRenderTarget方法, 使得arcgis js的离屏渲染逻辑能正常进行
                        const originalSetRenderTarget = renderer.setRenderTarget.bind(renderer);
                        renderer.setRenderTarget = function (target) {
                            originalSetRenderTarget(target);
                            if (target == null) {
                                context.bindRenderTarget();
                            }
                        };

                        // 创建threejs的scene对象
                        const scene = new THREE.Scene();
                        this.threejsObj.scene = scene;

                        // 创建threejs的摄像头
                        const camera = new THREE.PerspectiveCamera();
                        this.threejsObj.camera = camera;

                        // 添加坐标轴辅助工具
                        const axesHelper = new THREE.AxesHelper(10000000);
                        scene.add(axesHelper);

                        // 构建用于展示的几何体
                        const planeWidth = 100000;
                        const planeHeight = 100000;
                        const widthSegments = 300;
                        const heightSegments = 300;
                        const geometry = new THREE.PlaneBufferGeometry(planeWidth, planeHeight, widthSegments, heightSegments);
                        this.threejsObj.geometry = geometry;

                        // 创建热力图和灰度图的贴图纹理
                        const textureHeatmap = new THREE.Texture(heatmap._config.container.children[0]);
                        textureHeatmap.needsUpdate = true;
                        const textureGreyMap = new THREE.Texture(greyMap._config.container.children[0]);
                        textureGreyMap.needsUpdate = true;
                        // 构建几何体的材质
                        const material = new THREE.ShaderMaterial({
                            transparent: true,
                            vertexShader: myShader.vertexShader,
                            fragmentShader: myShader.fragmentShader,
                            uniforms: {
                                "u_zScale": {
                                    value: 10000.0
                                },
                                "u_heatMap": {
                                    value: textureHeatmap
                                },
                                "u_greyMap": {
                                    value: textureGreyMap
                                },
                                "u_color": {
                                    value: new THREE.Color("rgb(255,255,255)")
                                },
                                "u_opacity": {
                                    value: 1.0
                                },
                            }
                        });
                        material.side = THREE.DoubleSide;
                        this.threejsObj.material = material;

                        // 构建threejs的mesh对象
                        const heatmap3d = new THREE.Mesh(this.threejsObj.geometry, this.threejsObj.material);
                        this.threejsObj.object3d = heatmap3d;
                        // 地理坐标转换至三维渲染坐标
                        const coordinates = [
                            12607204.18613881,
                            2647777.5617401367,
                            0
                        ];
                        let transform = new THREE.Matrix4();
                        transform.fromArray(
                            externalRenderers.renderCoordinateTransformAt(
                                view,
                                coordinates,
                                view.spatialReference,
                                new Array(16)
                            )
                        );
                        transform.decompose(heatmap3d.position, heatmap3d.quaternion, heatmap3d.scale);
                        // 添加到场景中
                        scene.add(heatmap3d);

                        // 创建环境光
                        const color = 0xFFFFFF;
                        const intensity = 1;
                        const light = new THREE.AmbientLight(color, 1);
                        this.threejsObj.light = light;
                        // 添加光源
                        scene.add(light);

                        // 重置arcgis js管理的WebGL状态
                        context.resetWebGLState();
                    },

                    render: function (context) {
                        // 更新threejs摄像头
                        const camera4arcgis = context.camera;
                        this.threejsObj.camera.position.set(...camera4arcgis.eye);
                        this.threejsObj.camera.up.set(...camera4arcgis.up);
                        this.threejsObj.camera.lookAt(new THREE.Vector3(...camera4arcgis.center));
                        this.threejsObj.camera.projectionMatrix.fromArray(camera4arcgis.projectionMatrix);

                        // 绘制threejs场景
                        this.threejsObj.renderer.state.reset();
                        this.threejsObj.renderer.render(this.threejsObj.scene, this.threejsObj.camera);

                        // 请求arcgis js重绘视图
                        externalRenderers.requestRender(view);

                        // 重置arcgis js管理的WebGL状态
                        context.resetWebGLState();
                    }
                };
                externalRenderers.add(view, myExternalRenderer);

                // view.on("click", event => {
                //     console.log(event);
                //     console.log(view);
                // });
            };

            init();
        });
    </script>
</head>

<body>
    <div id="viewDiv"></div>
</body>

</html>